package com.wl.dzt.sys.util;

import com.wl.dzt.sys.annotation.Column;
import com.wl.dzt.sys.annotation.Transient;
import com.wl.dzt.sys.util.clazz.ClassUtil;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

/**
 * @author wl_code@163.com
 * @version 1.0
 * @date 2018/7/5/005
 * @description 字段工具
 */
public class FieldUtil {

    /**
     * 根据点级联获取
     *
     * @param clazz
     * @param field
     * @return
     */
    public static CascadeField getFieldByCascade(Class<?> clazz, String field) {
        Field result = null;
        Class<?> nowClazz = clazz;
        CascadeField cascadeField = new CascadeField();
        cascadeField.setClazz(nowClazz);
        for (String f : field.split("[.]")) {
            result = get(nowClazz, f);
            if (result != null) {
                Class<?> type = result.getType();
                if (!ClassUtil.isBaseValType(type)) {
                    nowClazz = type;
                    cascadeField.setClazz(nowClazz);
                }
            }
        }
        cascadeField.setField(result);
        return cascadeField;
    }

    /**
     * 获取字段值根据级联
     *
     * @param field
     * @param entity
     * @return
     */
    public static Object getFieldValueByCascade(String field, Object entity) {
        Field result = null;
        Class<?> nowClazz = entity.getClass();
        Object src = entity;
        for (String f : field.split("[.]")) {
            result = get(nowClazz, f);
            Class<?> type = result.getType();
            src = doGetMethod(src, result.getName());
            if (src == null) {
                return null;
            }
            if (!ClassUtil.isBaseValType(type)) {
                nowClazz = type;
            } else {
                return src;
            }
        }
        return null;
    }

    /**
     * 通过类和字段名称获取字段
     *
     * @param clazz
     * @param field
     * @return
     */
    public static Field get(Class<?> clazz, String field) {
        if (LogicUtil.empty(field)) {
            return null;
        }
        try {
            return clazz.getDeclaredField(field);
        } catch (NoSuchFieldException | SecurityException e) {
            // e.printStackTrace();
            return null;
        }
    }

    /**
     * 执行对象的get方法
     *
     * @param entity
     * @param field
     * @return
     */
    public static Object doGetMethod(Object entity, String field) {
        try {
            Method method = entity.getClass().getDeclaredMethod("get" + StringUtil.fristUpperCase(field));
            return method.invoke(entity);
        } catch (NoSuchMethodException | SecurityException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 级联设置值
     * 如果有空对象则自动new
     *
     * @param entity
     * @param field
     * @param value
     * @throws IllegalArgumentException
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public static void setValueByCascade(Object entity, String field, Object value) throws IllegalArgumentException,
            IllegalAccessException, InstantiationException {
        Class<?> clazz = entity.getClass();
        Object e = entity;
        for (String f : field.split("[.]")) {
            Field myField = get(clazz, f);
            if (myField == null) {
                return;
            }
            clazz = myField.getType();
            myField.setAccessible(true);
            if (ClassUtil.isBaseValType(clazz)) {
                myField.set(e, value);
            } else {
                Object object = doGetMethod(e, f);
                if (object == null) {
                    object = clazz.newInstance();
                    myField.set(e, object);
                }
                e = object;
            }
        }
    }

    /**
     * 判断某个对象是否存在集合并且不为空
     *
     * @param entity
     * @return
     */
    public static List<RefSetInfo> exitsSet(Object entity, int state) {
        List<RefSetInfo> list = new ArrayList<>();
        for (Field field : entity.getClass().getDeclaredFields()) {
            Class<?> clazz = field.getType();
            Transient trt = field.getAnnotation(Transient.class);
            if (clazz == Set.class && trt != null && !"".equals(trt.refField())) {
                try {
                    field.setAccessible(true);
                    Set<?> set = (Set<?>) field.get(entity);
                    RefSetInfo info = new RefSetInfo();
                    info.set = set;
                    info.refField = trt.refField();
                    info.srcClass = trt.srcClass();
                    list.add(info);
                } catch (IllegalArgumentException | IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        return list;
    }

    /**
     * 获取对象set 字段
     *
     * @param entity
     * @return
     */
    public static List<RefSetInfo> getEntitySet(Object entity) {
        List<RefSetInfo> data = new ArrayList<>();
        for (Field field : entity.getClass().getDeclaredFields()) {
            Transient trt = field.getAnnotation(Transient.class);
            if (trt != null) {
                RefSetInfo info = new RefSetInfo();
                info.srcClass = trt.srcClass();
                info.srcField = field.getName();
                info.refField = trt.refField();
                info.autoLoad = trt.autoLoad();
                info.fields = trt.fields();
                data.add(info);
            }
        }
        return data;
    }

    /**
     * 获取实体值类型字段
     *
     * @param clazz
     * @return
     */
    public static String[] entityValueFields(Class<?> clazz) {
        List<String> list = new ArrayList<>();
        Arrays.stream(clazz.getDeclaredFields()).forEach(
                f -> {
                    Column column = f.getAnnotation(Column.class);
                    if ((ClassUtil.isBaseValType(f.getType()) && column == null)
                            || (ClassUtil.isBaseValType(f.getType()) && column != null && !column.lazy())) {
                        list.add(f.getName());
                    }
                    if (f.getAnnotation(Transient.class) != null) {
                        list.remove(f.getName());
                    }
                });
        return list.toArray(new String[list.size()]);
    }

    public static class CascadeField {
        private Class<?> clazz;
        private Field field;

        public Class<?> getClazz() {
            return clazz;
        }

        public void setClazz(Class<?> clazz) {
            this.clazz = clazz;
        }

        public Field getField() {
            return field;
        }

        public void setField(Field field) {
            this.field = field;
        }

    }

    public static class RefSetInfo {
        private Set<?> set;
        private String refField;
        private Class<?> srcClass;
        private String srcField;
        private boolean autoLoad;
        private String[] fields;

        public Set<?> getSet() {
            return set;
        }

        public void setSet(Set<?> set) {
            this.set = set;
        }

        public String getRefField() {
            return refField;
        }

        public void setRefField(String refField) {
            this.refField = refField;
        }

        public Class<?> getSrcClass() {
            return srcClass;
        }

        public void setSrcClass(Class<?> srcClass) {
            this.srcClass = srcClass;
        }

        public String getSrcField() {
            return srcField;
        }

        public void setSrcField(String srcField) {
            this.srcField = srcField;
        }

        public boolean isAutoLoad() {
            return autoLoad;
        }

        public void setAutoLoad(boolean autoLoad) {
            this.autoLoad = autoLoad;
        }

        public String[] getFields() {
            return fields;
        }

        public void setFields(String[] fields) {
            this.fields = fields;
        }

    }
}
